package edu.xidian.onlinedocument.utils.ot.internal;

import edu.xidian.onlinedocument.utils.ot.internal.operations.*;

import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import java.lang.Math.*;

/**
 * 合并原始操作A以及进行transform(A,B)得到的B
 * 使得 apply(apply(S,A), B') = apply(S, apply(compose(A, B')))，即将AB'合并为一个操作
 */
public class Compose {
    private Compose() {
    }

    public static ChangesList compose(ChangesList left, ChangesList right) {

        ListIterator<Change> i = new LinkedList<>(left.getChanges()).listIterator();
        ListIterator<Change> j = new LinkedList<>(right.getChanges()).listIterator();
        List<Change> result = new LinkedList<>();
        while (i.hasNext() && j.hasNext()) {
            Change ch1 = i.next();
            Change ch2 = j.next();
            if (ChangeType.isRetain(ch1)) {
                if (ChangeType.isRetain(ch2)) {
                    if (ch1.offset().equals(ch2.offset())) {
                        result.add(ch1);
                    } else if (ch1.offset() < ch2.offset()) {
                        result.add(ch1);
                        j.set(new Retain(ch2.offset() - ch1.offset()));
                        j.previous();
                    } else if (ch1.offset() > ch2.offset()) {
                        result.add(ch2);
                        i.set(new Retain(ch1.offset() - ch2.offset()));
                        i.previous();
                    }
                }
                else if (ChangeType.isInsert(ch2)) {
                    result.add(ch2);
                    i.set(new Retain(ch1.offset() - ch2.offset()));
                    i.previous();
                }
                else if (ChangeType.isDelete(ch2)) {
                    result.add(ch2);
                    int len = ((Delete) ch2).offset();
                    if (len < ch1.offset()) {
                        i.set(new Retain(ch1.offset() - len));
                        i.previous();
                    }
                }
            }
            else if (ChangeType.isDelete(ch1)) {
                if (ChangeType.isRetain(ch2)) {
                    result.add(ch1);
                    j.previous();
                }
                else if (ChangeType.isInsert(ch2)) {
                    result.add(ch1);
                    result.add(ch2);
                }
                else if (ChangeType.isDelete(ch2)) {
                    result.add(ch1);
                    j.previous();
                }
            }
            else if (ChangeType.isInsert(ch1)) {
                if (ChangeType.isRetain(ch2)) {
                    result.add(ch1);
                    j.set(new Retain(ch2.offset() - ch1.offset()));
                    j.previous();
                }
                else if (ChangeType.isInsert(ch2)) {
                    if (left.getRevision() <= right.getRevision()) {
                        result.add(new Insert(((Insert) ch1).value() + ((Insert) ch2).value()));
                    }
                    else {
                        result.add(new Insert(((Insert) ch2).value() + ((Insert) ch1).value()));
                    }
                }
                else if (ChangeType.isDelete(ch2)) {
                    Insert i1 = (Insert) ch1;
                    Delete d2 = (Delete) ch2;
                    if (i1.offset() > d2.offset()) {
                        result.add(new Insert(i1.value().substring(d2.offset())));
                    }
                }
            }
        }

        while (i.hasNext()) {
            result.add(i.next());
        }
        while (j.hasNext()) {
            result.add(j.next());
        }

        return new ChangesList(result);
    }
}
